EJERCICIO 1

  • Escribe los metodos __repr__ y __str__ para la clase Array de forma que se imprima legiblemente como en numpy arrays.

In [1]:
class Array:
    "Una clase minima para algebra lineal"    
    def __init__(self, list_of_rows): 
        "Constructor y validador"
        # obtener dimensiones
        self.data = list_of_rows
        nrow = len(list_of_rows)
        #  ___caso vector: redimensionar correctamente
        if not isinstance(list_of_rows[0], list):
            nrow = 1
            self.data = [[x] for x in list_of_rows]
        # ahora las columnas deben estar bien aunque sea un vector
        ncol = len(self.data[0])
        self.shape = (nrow, ncol)
        # validar tamano correcto de filas
        if any([len(r) != ncol for r in self.data]):
            raise Exception("Las filas deben ser del mismo tamano")
    def __repr__(self):
        self.repr = ""
        for i in range((self.shape[0]-1)):
            self.repr += str(self.data[i]) + ',' + '\n'
        self.repr = self.repr + str(self.data[self.shape[0]-1])
        return "([" + self.repr + "])"
    def __str__(self):
        self.stru = ""
        self.str = ""
        for j in range(self.shape[0]-1):
            self.str = ""
            for i in range(self.shape[1]):
                self.str += str(self.data[j][i]) + ' '
            self.stru += self.str + '\n'
            self.str = ""
        for i in range(self.shape[1]):
            self.str += str(self.data[self.shape[0]-1][i]) + ' '
        self.stru = self.stru + self.str

        return "[ " + str(self.stru) +"]"

In [2]:
A = Array([[1,2,3,4],[4,5,6,7]])

In [3]:
A


Out[3]:
([[1, 2, 3, 4],
[4, 5, 6, 7]])

In [4]:
print(A)


[ 1 2 3 4 
4 5 6 7 ]

EJERCICIO 2

  • Escribe el metodo __setitem__ para que el codigo A[i,j] = new_value cambie el valor de la entrada (i,j) del array. El esqueleto de la funcion es
    class Array:
      #
      #
      def __setitem__(self, idx, new_value):
          "Ejercicio"
      #
    

In [410]:
class Array:
    "Una clase minima para algebra lineal"    
    def __init__(self, list_of_rows): 
        "Constructor y validador"
        # obtener dimensiones
        self.data = list_of_rows
        nrow = len(list_of_rows)
        #  ___caso vector: redimensionar correctamente
        if not isinstance(list_of_rows[0], list):
            nrow = 1
            self.data = [[x] for x in list_of_rows]
        # ahora las columnas deben estar bien aunque sea un vector
        ncol = len(self.data[0])
        self.shape = (nrow, ncol)
        # validar tamano correcto de filas
        if any([len(r) != ncol for r in self.data]):
            raise Exception("Las filas deben ser del mismo tamano")
    def __getitem__(self, idx):
        return self.data[idx[0]][idx[1]]
    def __setitem__(self,idx,new_value):
        self.data[idx[0]][idx[1]] = new_value

In [6]:
A = Array([[1,2,3],[4,5,6]])

In [7]:
A[0,1]


Out[7]:
2

In [8]:
A[0,1] = 3

In [9]:
A[0,1]


Out[9]:
3

EJERCICIO 3

  1. Implementa una funcion de zeros para crear arrays "vacios"
    def zeros(shape):
     "Implementame por favor"
    
    Hint, encontraras utiles las listas de comprehension, por ejemplo el codigo [0. for x in range(5)] crea una lista de 5 ceros.
  2. Implementa una funcion eye(n) que crea la matriz identidad de $n\times n$ (es decir, la matriz que tiene puros ceros y unos en la diagonal). El nombre eye es tradicional en software de algebra lineal, aunque no es muy intuitivo.

In [10]:
class Array:
    def __init__(self, list_of_rows):
        "Constructor y validador"
        # obtener dimensiones
        self.data = list_of_rows
        self.shape = (len(list_of_rows), len(list_of_rows[0]))
        nrow = len(list_of_rows)
        #  ___caso vector: redimensionar correctamente
        if not isinstance(list_of_rows[0], list):
            nrow = 1
            self.data = [[x] for x in list_of_rows]
        # ahora las columnas deben estar bien aunque sea un vector
        ncol = len(self.data[0])
        self.shape = (nrow, ncol)
        # validar tamano correcto de filas
        if any([len(r) != ncol for r in self.data]):
            raise Exception("Las filas deben ser del mismo tamano")
    def zeros(self,r,c):
        self.columnas = c
        self.filas = r
        self.zeros = [[0 for col in range(c)] for row in range(r)]
        return self.zeros
    def eye(self,n):
        self.zeros = [[0 for col in range(n)] for row in range(n)]
        for i in range(n):
            self.zeros[i][i] = 1
        return self.zeros

In [11]:
A = Array([[1,2,3],[4,5,6]])

In [12]:
A.zeros(2,3)


Out[12]:
[[0, 0, 0], [0, 0, 0]]

In [13]:
A.eye(2)


Out[13]:
[[1, 0], [0, 1]]

In [14]:
class Zeros:
    def __init__(self,r,c):
        self.columnas = c
        self.filas = r
        self.data = [[0 for col in range(c)] for row in range(r)]
    def __repr__(self):
        self.repr = ""
        for i in range((self.filas-1)):
            self.repr += str(self.data[i]) + ',' + '\n'
        self.repr = self.repr + str(self.data[self.filas-1])
        return "([" + self.repr + "])"
    def __str__(self):
        self.stru = ""
        self.str = ""
        for j in range(self.filas-1):
            self.str = ""
            for i in range(self.columnas):
                self.str += str(self.data[j][i]) + ' '
            self.stru += self.str + '\n'
            self.str = ""
        for i in range(self.columnas):
            self.str += str(self.data[self.filas-1][i]) + ' '
        self.stru = self.stru + self.str
        return "[ " + str(self.stru) +"]"

In [15]:
x = Zeros(5,6)

In [16]:
x


Out[16]:
([[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]])

In [17]:
print(x)


[ 0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 ]

In [18]:
class eye:
    def __init__(self,n):
        self.dim = n
        self.data = [[0 for col in range(n)] for row in range(n)]
        for i in range(n):
            self.data[i][i] = 1
    def __repr__(self):
        self.repr = ""
        for i in range((self.dim-1)):
            self.repr += str(self.data[i]) + ',' + '\n'
        self.repr = self.repr + str(self.data[self.dim-1])
        return "([" + self.repr + "])"
    def __str__(self):
        self.stru = ""
        self.str = ""
        for j in range(self.dim-1):
            self.str = ""
            for i in range(self.dim):
                self.str += str(self.data[j][i]) + ' '
            self.stru += self.str + '\n'
            self.str = ""
        for i in range(self.dim):
            self.str += str(self.data[self.dim-1][i]) + ' '
        self.stru = self.stru + self.str
        return "[ " + str(self.stru) +"]"

In [19]:
y = eye(2)

In [20]:
y


Out[20]:
([[1, 0],
[0, 1]])

In [21]:
print(y)


[ 1 0 
0 1 ]

EJERCICIO 4

  • Implementa la funcion de transposicion
    class Array:
      ###
      ###
      ###
      def transpose(self):
          "Implementame :)"
          ###
    

In [22]:
class Array:
    "Una clase minima para algebra lineal"    
    def __init__(self, list_of_rows):
        "Constructor y validador"
        # obtener dimensiones
        self.data = list_of_rows
        self.shape = (len(list_of_rows), len(list_of_rows[0]))
        nrow = len(list_of_rows)
        #  ___caso vector: redimensionar correctamente
        if not isinstance(list_of_rows[0], list):
            nrow = 1
            self.data = [[x] for x in list_of_rows]
        # ahora las columnas deben estar bien aunque sea un vector
        ncol = len(self.data[0])
        self.shape = (nrow, ncol)
        # validar tamano correcto de filas
        if any([len(r) != ncol for r in self.data]):
            raise Exception("Las filas deben ser del mismo tamano")

    def transpose(self):
        self.fila = self.shape[0]
        self.columna = self.shape[1]
        self.row = [[0 for col in range(self.fila)] for row in range(self.columna)]
        for j in range(self.columna):
            for i in range(self.fila):
                self.row[j][i] = self.data[i][j]
        return self.row

In [23]:
A = Array([[1,2,3,4],[5,6,7,8],[9,10,11,12]])

In [24]:
A.transpose()


Out[24]:
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

EJERCICIO 5

  1. (dificil) En nuestra clase Array la expresion A + 1 tiene sentido para un Array A, sin embargo la expresion inversa falla, por ejemplo
    1 + Array([[1,2], [3,4]])
    
    entrega un error. Investiga como implementar el metodo de clase __radd__ para resolver este problema.
  2. Nuestro metodo de suma no sabe restar, implementa el metodo de clase __sub__ similar a la suma para poder calcular expresiones como A - B para A y B arrays o numeros.

In [25]:
class Array:
    "Una clase minima para algebra lineal"    
    def __init__(self, list_of_rows): 
        "Constructor y validador"
        # obtener dimensiones
        self.data = list_of_rows
        nrow = len(list_of_rows)
        #  ___caso vector: redimensionar correctamente
        if not isinstance(list_of_rows[0], list):
            nrow = 1
            self.data = [[x] for x in list_of_rows]
        # ahora las columnas deben estar bien aunque sea un vector
        ncol = len(self.data[0])
        self.shape = (nrow, ncol)
        # validar tamano correcto de filas
        if any([len(r) != ncol for r in self.data]):
            raise Exception("Las filas deben ser del mismo tamano")
        
    def __add__(self, other):
        if isinstance(other, Array):
            if self.shape != other.shape:
                raise Exception("Las dimensiones son distintas!")
            rows, cols = self.shape
            newArray = Array([[0. for c in range(cols)] for r in range(rows)])
            for r in range(rows):
                for c in range(cols):
                    newArray.data[r][c] = self.data[r][c] + other.data[r][c]
            return newArray
        elif isinstance(2, (int, float, complex)): # en caso de que el lado derecho sea solo un numero
            rows, cols = self.shape
            newArray = Array([[0. for c in range(cols)] for r in range(rows)])
            for r in range(rows):
                for c in range(cols):
                    newArray.data[r][c] = self.data[r][c] + other
            return newArray
        else:
            return NotImplemented # es un tipo de error particular usado en estos metodos
    "Parte 1"
    __radd__ = __add__
    
    "Parte 2"
    def __sub__(self, other):
        if isinstance(other, Array):
            if self.shape != other.shape:
                raise Exception("Las dimensiones son distintas!")
            rows, cols = self.shape
            newArray = Array([[0. for c in range(cols)] for r in range(rows)])
            for r in range(rows):
                for c in range(cols):
                    newArray.data[r][c] = self.data[r][c] - other.data[r][c]
            return newArray
        elif isinstance(2, (int, float, complex)): # en caso de que el lado derecho sea solo un numero
            rows, cols = self.shape
            newArray = Array([[0. for c in range(cols)] for r in range(rows)])
            for r in range(rows):
                for c in range(cols):
                    newArray.data[r][c] = self.data[r][c] - other
            return newArray
        else:
            return NotImplemented # es un tipo de error particular usado en estos metodos

    def __rsub__(self, other):
        if isinstance(2, (int, float, complex)): # en caso de que el lado derecho sea solo un numero
            rows, cols = self.shape
            newArray = Array([[0. for c in range(cols)] for r in range(rows)])
            for r in range(rows):
                for c in range(cols):
                    newArray.data[r][c] = other - self.data[r][c]
            return newArray
        else:
            return NotImplemented # es un tipo de error particular usado en estos metodos

In [26]:
A = Array([[1,2,3],[4,5,6]])

In [27]:
B = Array([[4,5,6],[7,8,9]])

In [28]:
C = A + B

In [29]:
C.data


Out[29]:
[[5, 7, 9], [11, 13, 15]]

In [30]:
D = A + 2

In [31]:
D.data


Out[31]:
[[3, 4, 5], [6, 7, 8]]

In [32]:
E = 3 + B

In [33]:
E.data


Out[33]:
[[7, 8, 9], [10, 11, 12]]

In [34]:
F = A - B

In [35]:
F.data


Out[35]:
[[-3, -3, -3], [-3, -3, -3]]

In [36]:
G = B - 3

In [37]:
G.data


Out[37]:
[[1, 2, 3], [4, 5, 6]]

In [38]:
H = 10 - A

In [39]:
H.data


Out[39]:
[[9, 8, 7], [6, 5, 4]]

EJERCICIO 6

Implementa las funciones __mul__ y __rmul__ para hacer multiplicacion matricial (y por un escalar). Hint. Entiende y modifica el codigo de suma del punto anterior


In [10]:
class Array:
    def __init__(self, list_of_rows): 
        "Constructor y validador"
        # obtener dimensiones
        self.data = list_of_rows
        nrow = len(list_of_rows)
        #  ___caso vector: redimensionar correctamente
        if not isinstance(list_of_rows[0], list):
            nrow = 1
            self.data = [[x] for x in list_of_rows]
        # ahora las columnas deben estar bien aunque sea un vector
        ncol = len(self.data[0])
        self.shape = (nrow, ncol)
        # validar tamano correcto de filas
        if any([len(r) != ncol for r in self.data]):
            raise Exception("Las filas deben ser del mismo tamano")
        
    def __mul__(self, other):
        if isinstance(other, Array):
            if self.shape[1] != other.shape[0]:
                raise Exception("Las dimensiones no coinciden!")
            rowsarr = self.shape[0]
            coincidir = self.shape[1]
            colsoth = other.shape[1]
            val = 0
            newArray = Array([[0. for c in range(colsoth)] for r in range(rowsarr)])
            for r in range(rowsarr):
                for c in range(colsoth):
                    val = 0
                    for i in range(coincidir):
                        val += self.data[r][i] * other.data[i][c]
                    newArray.data[r][c] = val
            return newArray
        elif isinstance(2, (int, float, complex)): # en caso de que el lado derecho sea solo un numero
            rows, cols = self.shape
            newArray = Array([[0. for c in range(cols)] for r in range(rows)])
            for r in range(rows):
                for c in range(cols):
                    newArray.data[r][c] = self.data[r][c] * other
            return newArray
        else:
            return NotImplemented # es un tipo de error particular usado en estos metodos
    __rmul__ = __mul__

In [11]:
A = Array([[1,2],[4,5],[7,8]])

In [12]:
B = Array([[4,5,6],[7,8,9]])

In [14]:
C = A * B

In [15]:
C.data


Out[15]:
[[18, 21, 24], [51, 60, 69], [84, 99, 114]]

In [17]:
D = A * 2

In [18]:
D.data


Out[18]:
[[2, 4], [8, 10], [14, 16]]

In [21]:
E = -2 * B

In [22]:
E.data


Out[22]:
[[-8, -10, -12], [-14, -16, -18]]

EJERCICIOS 7-10

Implementa una funcion forward_subs que resuelva sistemas de ecuaciones de la forma $Lx = y$ con $L$ triangular inferior y $y$ cualquier Vector o Array de una columna.

Implementa una funcion backward_subs que resuelva sistemas $Ux = y$ con $U$ triangular superior y y Vector o Array de una columna.

Implementa una funcion LU que reciba un Array $A$ y devuelva 3 arrays $L$,$U$ y $P$ tales que $PA = LU$ con $L$ trangular inferior, $U$ triangular superior y $P$ matriz de permutacion.

Implementa una funcion lu_linsolve que resuelva cualquier sistema de ecuaciones Ax = y con A un Array y y un Vector o Array de una columna. La solución es muy sencilla usando la descomposición LU de los puntos anteriores.


In [558]:
class Array:
    def __init__(self, L): 
        "Constructor y validador"
        # obtener dimensiones
        self.data = L
        nrow = len(L)
        #  ___caso vector: redimensionar correctamente
        if not isinstance(L[0], list):
            nrow = 1
            self.data = [[x] for x in L]
        # ahora las columnas deben estar bien aunque sea un vector
        ncol = len(self.data[0])
        self.shape = (nrow, ncol)
        # validar tamano correcto de filas
        if any([len(r) != ncol for r in self.data]):
            raise Exception("Las filas deben ser del mismo tamano")
        
    def __repr__(self):
        self.repr = ""
        for i in range((self.shape[0]-1)):
            self.repr += str(self.data[i]) + ',' + '\n'
        self.repr = self.repr + str(self.data[self.shape[0]-1])
        return "([" + self.repr + "])"

    def __str__(self):
        self.stru = ""
        self.str = ""
        for j in range(self.shape[0]-1):
            self.str = ""
            for i in range(self.shape[1]):
                self.str += str(self.data[j][i]) + ' '
            self.stru += self.str + '\n'
            self.str = ""
        for i in range(self.shape[1]):
            self.str += str(self.data[self.shape[0]-1][i]) + ' '
        self.stru = self.stru + self.str
        return "[ " + str(self.stru) +"]"
    
    def __add__(self, other):
        if isinstance(other, Array):
            if self.shape != other.shape:
                raise Exception("Las dimensiones son distintas!")
            rows, cols = self.shape
            newArray = Array([[0. for c in range(cols)] for r in range(rows)])
            for r in range(rows):
                for c in range(cols):
                    newArray.data[r][c] = self.data[r][c] + other.data[r][c]
            return newArray
        elif isinstance(2, (int, float, complex)): # en caso de que el lado derecho sea solo un numero
            rows, cols = self.shape
            newArray = Array([[0. for c in range(cols)] for r in range(rows)])
            for r in range(rows):
                for c in range(cols):
                    newArray.data[r][c] = self.data[r][c] + other
            return newArray
        else:
            return NotImplemented # es un tipo de error particular usado en estos metodos

    __radd__ = __add__
    
    def __sub__(self, other):
        if isinstance(other, Array):
            if self.shape != other.shape:
                raise Exception("Las dimensiones son distintas!")
            rows, cols = self.shape
            newArray = Array([[0. for c in range(cols)] for r in range(rows)])
            for r in range(rows):
                for c in range(cols):
                    newArray.data[r][c] = self.data[r][c] - other.data[r][c]
            return newArray
        elif isinstance(2, (int, float, complex)): # en caso de que el lado derecho sea solo un numero
            rows, cols = self.shape
            newArray = Array([[0. for c in range(cols)] for r in range(rows)])
            for r in range(rows):
                for c in range(cols):
                    newArray.data[r][c] = self.data[r][c] - other
            return newArray
        else:
            return NotImplemented # es un tipo de error particular usado en estos metodos

    def __rsub__(self, other):
        if isinstance(2, (int, float, complex)): # en caso de que el lado derecho sea solo un numero
            rows, cols = self.shape
            newArray = Array([[0. for c in range(cols)] for r in range(rows)])
            for r in range(rows):
                for c in range(cols):
                    newArray.data[r][c] = other - self.data[r][c]
            return newArray
        else:
            return NotImplemented # es un tipo de error particular usado en estos metodos
        
    def __mul__(self, other):
        if isinstance(other, Array):
            if self.shape[1] != other.shape[0]:
                raise Exception("Las dimensiones no coinciden!")
            rowsarr = self.shape[0]
            coincidir = self.shape[1]
            colsoth = other.shape[1]
            val = 0
            newArray = Array([[0. for c in range(colsoth)] for r in range(rowsarr)])
            for r in range(rowsarr):
                for c in range(colsoth):
                    val = 0
                    for i in range(coincidir):
                        val += self.data[r][i] * other.data[i][c]
                    newArray.data[r][c] = val
            return newArray
        elif isinstance(2, (int, float, complex)): # en caso de que el lado derecho sea solo un numero
            rows, cols = self.shape
            newArray = Array([[0. for c in range(cols)] for r in range(rows)])
            for r in range(rows):
                for c in range(cols):
                    newArray.data[r][c] = self.data[r][c] * other
            return newArray
        else:
            return NotImplemented # es un tipo de error particular usado en estos metodos
    __rmul__ = __mul__
    
    def __getitem__(self, idx):
        return self.data[idx[0]][idx[1]]
    
    def __setitem__(self,idx,new_value):
        self.data[idx[0]][idx[1]] = new_value

    # Ejercicio 7
    def forward_subs(self, y):
        L = self.data
        nrow = len(L)
        ncol = len(L[0])
        
        if (nrow != ncol):
            raise Exception("La matriz debe ser cuadrada")
        
        for i in range(0,nrow):
            for j in range(0,ncol):
                if i < j:
                    if self.data[i][j] != 0:
                        raise Exception("No es matriz triangular inferior")
                        
        x = []
        for i in range(nrow):
            x.append(y[i,0])
            for j in range(i):
                x[i] -=  (L[i][j] * x[j])
            x[i] /= L[i][i]
        return x

    # Ejercicio 8
    def backward_subs(self, y):
        U = self.data
        nrow = len(U)
        ncol = len(U[0])
        
        if (nrow != ncol):
            raise Exception("La matriz debe ser cuadrada")
        
        for i in range(0,nrow):
            for j in range(0,ncol):
                if i > j:
                    if self.data[i][j] != 0:
                        raise Exception("No es matriz triangular superior")
        
        x = [0 for k in range(nrow)]
        x[nrow - 1] = y[nrow - 1,0] / U[nrow - 1][nrow - 1]
        for i in range(nrow - 2,-1,-1):
            m = y[i,0]
            for j in range(i + 1, nrow):
               m += x[i] - U[i][j] * x[j]
            x[i] = m / U[i][i]
        return x
        
    # Ejercicio 9   
    def decompLU(self):
        A = self.data
        nrow = len(A)
        ncol = len(A[0])
        
        P = [[0 for col in range(ncol)] for row in range(nrow)]
        L = [[0 for col in range(ncol)] for row in range(nrow)]
        U = [[0 for col in range(ncol)] for row in range(nrow)]
                        
        # P
        for i in range(nrow):
            P[i][i]= 1
        
        for k in range(nrow):
            r = max(range(k,nrow),key = lambda i: abs(A[i][k]))
            if k != r:
                P[k], P[r] = P[r], P[k]
        
        P = Array(P)
        
        PA = P * Array(A)
        
        for j in range(nrow):
            L[j][j] = 1
            for i in range(j + 1):
                s1 = sum(L[i][k]*U[k][j] for k in range(i))
                U[i][j] = PA[i,j] - s1
            for i in range(j,nrow):
                s2 = sum(U[k][j]*L[i][k] for k in range(j))
                L[i][j] = (PA[i,j] - s2) / U[j][j]
        
        L = Array(L)
        U = Array(U)
        
        return L,U,P
    
    # Ejercicio 10
    def lu_linsolve(self,b):
        A = Array(self.data)
        L = A.decompLU()[0]
        U = A.decompLU()[1]
        P = A.decompLU()[2]
        
        y = L.forward_subs(b)
        nrow = A.shape[0]
        
        y1 = b        
        for i in range(nrow):
            y1[i,0] = y[i]    
        
        x = U.backward_subs(y1)
        
        x1 = b        
        for i in range(nrow):
            x1[i,0] = x[i]
        
        return x

In [559]:
# Triangular Inferior
# Metodo Forward
B = Array([[3,0,0,0],[34,12,0,0],[56,12,123,0],[12,20,540,11]])
B


Out[559]:
([[3, 0, 0, 0],
[34, 12, 0, 0],
[56, 12, 123, 0],
[12, 20, 540, 11]])

In [560]:
B.forward_subs(Array([[23],[20],[35],[46]]))


Out[560]:
[7.666666666666667,
 -20.055555555555557,
 -1.2493224932249325,
 93.61320522296133]

In [561]:
# Triangular Superior
# Metodo Backward
C = Array([[3,1,4,6],[0,1,34,12],[0,0,123,12],[0,0,0,11]])
C


Out[561]:
([[3, 1, 4, 6],
[0, 1, 34, 12],
[0, 0, 123, 12],
[0, 0, 0, 11]])

In [562]:
C.backward_subs(Array([[23],[20],[35],[46]]))


Out[562]:
[8.129342202512934,
 -25.98521803399852,
 -0.12342941611234293,
 4.181818181818182]

In [563]:
# Descomposicion PA = LU
A = Array([[1,1,1,4],[20,1,5,-11],[5,-4,3,2],[20,30,-12,11]])

In [564]:
LUP = A.decompLU()
L = LUP[0]
L


Out[564]:
([[1.0, 0, 0, 0],
[1.0, 1.0, 0, 0],
[0.05, 0.03275862068965517, 1.0, 0],
[0.25, -0.14655172413793102, -0.5672823218997359, 1.0]])

In [565]:
U = LUP[1]
U


Out[565]:
([[20, 1, 5, -11],
[0, 29.0, -17.0, 22.0],
[0, 0, 1.306896551724138, 3.8293103448275865],
[0, 0, 0, 10.146437994722953]])

In [566]:
P = LUP[2]
P


Out[566]:
([[0, 1, 0, 0],
[0, 0, 0, 1],
[1, 0, 0, 0],
[0, 0, 1, 0]])

In [567]:
A = Array([[1,1,1,4],[20,1,5,-11],[5,-4,3,2],[20,30,-12,11]])
b = Array([[1],[2],[3],[4]])
x = A.lu_linsolve(b)
# Solucion de PAx = b
x


Out[567]:
[0.19425302301391234,
 -0.011832011441945081,
 0.6290469379794567,
 0.5471330126121441]